Skip to content

feat(Tearsheet): Implement new Tearsheet component - #954

Open
GAUNSD wants to merge 9 commits into
patternfly:mainfrom
GAUNSD:gmurcia/tearsheet
Open

feat(Tearsheet): Implement new Tearsheet component#954
GAUNSD wants to merge 9 commits into
patternfly:mainfrom
GAUNSD:gmurcia/tearsheet

Conversation

@GAUNSD

@GAUNSD GAUNSD commented Jul 9, 2026

Copy link
Copy Markdown

Issues

Summary

Implementation of Tearsheet as a set of components in react-component-groups

Changes

  • 🆕 New components: <Tearsheet>, <TearsheetBody>, <TearsheetFooter>, <TearsheetHeader>, and <TearsheetGroup>
  • 🆕 New documentation for use of <Tearsheet>
  • 🔀 Tweaked build scripts to support Windows
    • I'm running locally on Windows where path separators are \ instead of the /.
    • While running locally, some build scripts fail because of this and tweaks were made in order to get a successful build.
    • Changed files:
      • packages/module/utils.js (Common Windows support for paths)
      • packages/module/generate-fed-package-json.js
      • packages/module/generate-index.js
      • packages/module/patternfly-docs/patternfly-docs.source.js
      • scripts/parse-dynamic-modules.mjs
  • 🧹 Small cleanup in README.md

Screenshots

Documentation Basic Tearsheet Grid layout example Tearsheet stacking Tearsheet complex example

GAUNSD and others added 8 commits July 8, 2026 16:00
Generated-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
On Windows, Node's path.resolve and path.relative produce
backslash-separated paths (e.g. C:\src\*\index.ts). glob v10+
treats backslashes as escape characters rather than path separators,
so \* becomes a literal asterisk match instead of a wildcard. This
caused generate-index.js to find zero source files, producing an
empty src/index.ts, which compiled to an empty dist/esm/index.js.
The downstream build:fed:packages step then crashed with
"Cannot read properties of undefined (reading 'flags')" when the
TypeScript checker tried to get exports from a module with no symbol.

Even if the index had been populated, generate-fed-package-json.js
had the same glob issue — its patterns with process.cwd() backslashes
would match nothing on Windows, so no dist/dynamic/*/package.json
stubs would be created. The doc examples import from those stubs, so
the dev server would still show a blank page.

Additionally, path.relative on Windows returns backslash paths,
which broke the .replace('/dist', '') calls that strip the dist
prefix from relative paths written into generated package.json files.

Changes:
- Add packages/module/utils.js with cross-platform path utilities:
  toPosixPath (normalize separators), posixGlobSync (normalize glob
  pattern + results), and posixRelative (normalize path.relative
  output). Each function is documented with why it exists.
- Refactor generate-index.js to use posixGlobSync from utils.js
- Refactor generate-fed-package-json.js to use toPosixPath,
  posixGlobSync, and posixRelative from utils.js
- Normalize basePath and path.relative output inline in
  scripts/parse-dynamic-modules.mjs (kept inline since it lives in
  a separate shared scripts directory)

All changes are no-ops on Linux/macOS where paths already use
forward slashes.

Generated-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Doc site source discovery (patternfly-docs.source.js) passed raw
path.join() output into globSync(). On Windows, path.join() produces
backslash-separated paths (e.g. patternfly-docs\content\extensions\**\*.md).
glob v10+ treats backslashes as escape characters, not path separators,
so both the sourceProps and sourceMD globs silently matched zero files.
This caused the generated/index.js routes file to be empty, leaving
the entire Extensions sidenav blank on Windows dev servers.

Fix: wrap glob patterns with the existing toPosixPath() utility from
utils.js (created in d93b8fe for the same class of bug in other build
scripts).

Also mark className as optional in TearsheetBody, TearsheetFooter,
and TearsheetHeader props interfaces — className is passed through to
PF ModalBody/ModalFooter/ModalHeader which already default it.

Generated-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Assisted-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Tearsheet component:
- Remove bottom border radius so tearsheet sits flush against viewport edge
- Change tearsheetInner from height:100% to flex:1 1 auto + minHeight:0
  so it participates properly in the ModalBox flex layout

TearsheetBody component:
- Replace placeholder caretColor:red with flex:1, minHeight:0, overflow:auto
  so the body fills remaining space and scrolls independently

TearsheetGroup component:
- Remove placeholder caretColor:red style

Documentation (Tearsheet.md):
- Expand propComponents to include TearsheetHeader, TearsheetBody,
  TearsheetFooter, and TearsheetGroup
- Add descriptive text for each example section
- Reorder examples: Basic, Layouts, Stacked, Group, Comparison

TearsheetLayouts example:
- Remove 'simple' layout, rename 'xl-text' to 'long-text'
- Expand grid layout to 60 randomly sorted cards
- Rename 'long' label to 'Flex layout'

TearsheetComparison example (new):
- Side-by-side Tearsheet vs Modal (ModalVariant.large) demo showing
  why tearsheets are better for dense content
- Body content: sticky search bar (PageSection), vertical JumpLinks
  in a SidebarPanel, 3-column card grid with DescriptionLists,
  Labels, and CodeBlocks across 6 sections (36 cards total)
- CSS fix for sidebar scrolling: sidebar__main height:100%,
  sidebar__content overflow:scroll + height:100%

Generated-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
@GAUNSD GAUNSD changed the title feat(Tearsheet): feat(Tearsheet): Implement new Tearsheet component Jul 9, 2026
@GAUNSD
GAUNSD marked this pull request as ready for review July 10, 2026 00:31
@thatblindgeye

Copy link
Copy Markdown
Contributor

/deploy-preview

@patternfly-build

patternfly-build commented Jul 13, 2026

Copy link
Copy Markdown

@thatblindgeye thatblindgeye left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to file comments below, we need to bump to the following package versions if they're being used in the component-groups package jsons:

  • @patternfly/react-code-editor@6.6.2
  • @patternfly/react-core@6.6.2
  • @patternfly/react-docs@7.6.2
  • @patternfly/react-drag-drop@6.6.2
  • @patternfly/react-table@6.6.2
  • @patternfly/react-templates@6.6.2

Comment on lines +2 to +12
# Sidenav top-level section
# should be the same for all markdown files
section: extensions
subsection: component-groups
# Sidenav secondary level section
# should be the same for all markdown files
id: Tearsheet
# Tab (react | react-demos | html | html-demos | design-guidelines | accessibility)
source: react
# If you use typescript, the name of the interface to display props for
# These are found through the sourceProps function provided in patternfly-docs.source.js

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove these comments and just leave the property value pairs

import { createUseStyles } from 'react-jss';
import { Modal, ModalVariant } from '@patternfly/react-core';

const useStyles = createUseStyles({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcoker could you take a look at these new component files that are using useStyles? We likely can keep the styles inside these tsx files as there's convention in component-groups for it, so more if there's anything you think wew should tweak with the styles themselves.

Comment on lines +78 to +82
/** Accessible label for the tearsheet. */
'aria-label'?: string;
/** ID of the element that labels the tearsheet. */
'aria-labelledby'?: string;
/** ID of the element that describes the tearsheet. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** Accessible label for the tearsheet. */
'aria-label'?: string;
/** ID of the element that labels the tearsheet. */
'aria-labelledby'?: string;
/** ID of the element that describes the tearsheet. */
/** Accessible name for the tearsheet as a human readable string. */
'aria-label'?: string;
/** Space separated list of ID's of the elements that label the tearsheet. */
'aria-labelledby'?: string;
/** ISpace separated list of ID's of the elements that describe the tearsheet. */

Comment on lines +84 to +85
/** Flag to disable focus trap. */
disableFocusTrap?: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about instead of exposing this prop specifically for now, we just include a modalProps prop that cna be spread on Modal? Just wondering if we should explicitly expose this one for now since having no focus trap with multiple modals open might be even more hairy than just a single, non-focus trapped modal.


return (
<Modal
// animated // Depends on animated modal PR being merged in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this comment for now. Once the animation updates are done in core and your React PR can pull those in/be updated and merged, we can open a followup PR to add animation support in this repo

height: 'calc(100% - 4rem) !important',
maxHeight: 'calc(100% - 4rem) !important',
insetBlockStart: '2rem !important',
// top: '2rem !important',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these top styles only needed once animation support is available? If so, could we remove them and instead either mention in your React PR or in this one just mentioning what top/additional styles will be needed for animation support?

Comment on lines +14 to +16
export interface TearsheetBodyProps extends ModalBodyProps {
className?: string;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should include the children prop here and also provide descriptions. This will apply to the TearsheetFooter and TearsheetHeader component files as well.

},
});

/** The maximum number of visually distinct stack levels (0, 1, 2). */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we don't need this comment

Comment on lines +7 to +8
tearsheetGroup: {
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we plan to eventually add styles here, or is this more of an in case we ever decide to add any?

/** Additional classes added to the Tearsheet group. */
className?: string;
/** Unique id for the Tearsheet group. */
id: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we be able to provide a random ID if a consumer doesn't pass one, or is it important for consumers to pass a static, unique ID?

@kaylachumley kaylachumley left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this!
Left a bunch of content suggestions to match the patternfly voice and tone. @thatblindgeye feel free to chime in on any of those suggestions.

Also wanted to note that it looks like the tearsheets are using an outdated version of the "x" close icon button and needs to be swapped to the rh-icon equivalent.

Left a design recommendation as well. Tried to review as best I could, but just wanted to make note that if we can, lets make sure to use pf styling and no custom out of the box css (i didnt see anything crazy but just a general friendly note) 🤓


### Basic

Typical tearsheets should make use of the entire area, for this basic case some sample text is rendered.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Typical tearsheets should make use of the entire area, for this basic case some sample text is rendered.
Basic tearsheets should make use of the entire container. For this basic example, heading and body text is rendered with an action list placed within the footer area.


### Tearsheet layouts

Tearsheets should allow various sorts of layouts to be rendered.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Tearsheets should allow various sorts of layouts to be rendered.
Tearsheets can be customized to render various layout styles. These layout styles include, [full width text](https://www.patternfly.org/foundations-and-styles/utility-classes/alignment), [flex](https://www.patternfly.org/foundations-and-styles/layouts/flex), and [grid](https://www.patternfly.org/foundations-and-styles/layouts/grid).

Comment on lines +48 to +51
One special use case with tearsheets is stacking.
When a user is using a tearsheet, if another one needs to open it can open one level "on-top" of it in a new stack.
Tearsheets offer 3 stack levels (0,1,2).
A special stack level -1 allows a tearsheet to hide behind others.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
One special use case with tearsheets is stacking.
When a user is using a tearsheet, if another one needs to open it can open one level "on-top" of it in a new stack.
Tearsheets offer 3 stack levels (0,1,2).
A special stack level -1 allows a tearsheet to hide behind others.
Tearsheets support stacking, allowing new sheets to open on top of active ones. They utilize three visible stack levels (0, 1, and 2) and a background level (-1) to hide inactive sheets.

Comment on lines +56 to +62
### Tearsheet group (infinite stacking)

Use a `TearsheetGroup` to manage an unbounded number of stacked tearsheets.
`children` rendering order determines stacking priority with later children stacking in front of earlier ones.
Only the top 3 open tearsheets are visible; earlier ones hide behind the stack and reappear as front tearsheets are closed.

```ts file="./TearsheetGroup.tsx"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a design standpoint, I don't think its a good idea to recommend infinite stacking. I would advise against including this as an example so we dont encourage users to stack more than 4 sheets. I think it could be useful to include some explicit text in the stacking section that says something like "Limit stacked tearsheets to a maximum of three. Flows requiring four or more levels should be redesigned using multi-step wizard or dedicates page to prevent loss of user context."

Hiding an unbounded number of sheets behind the visible top 3 tends to break the user's mental model of where they are on the page, and forces "click-to-close" repetition to get back to the main view.

```

### Tearsheets vs Modals

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Choose a tearsheet over a modal when users need to process detailed and complex workflows. This example demonstrates how the expanded surface area of a tearsheet allows users to easily navigate a robust layout without feeling overwhelmed.

@mcoker mcoker left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments but it looks good to me as long as you want to lock the full-page and stacked offset styles down and not allow overrides.

maxWidth: 'calc(100% - 4rem) !important',
height: 'calc(100% - 4rem) !important',
maxHeight: 'calc(100% - 4rem) !important',
insetBlockStart: '2rem !important',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the only reason for the insetBlockStart is because the modal is center aligned vertically via align-items: center; on the parent. If you update this element to align-self: end;, you shouldn't need the insets on any of the tearsheet classes. And I might override align-self anyways because position="top" will apply align-self: flex-start and could break the tearsheet styling if applied. If you did that, I would still set insetBlockStart to auto here (instead of removing it) because position="top" also applies an inset-block-start you'll want to override.

Suggested change
insetBlockStart: '2rem !important',
insetBlockStart: 'auto !important',
alignSelf: 'end !important'


const useStyles = createUseStyles({
tearsheetHeader: {
flexShrink: 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const useStyles = createUseStyles({
tearsheetFooter: {
flexShrink: 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +7 to +11
tearsheetBody: {
flex: 1,
minHeight: 0,
overflow: 'auto',
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are pretty similar to what comes with ModalBody, with the primary difference being ModalBody maintains a super small min-height in case you shrink the window super small, you could still manage to see some of the body text and it hides x-axis overflow where this style allows it with a scrollbar.

// animated // Depends on animated modal PR being merged in
className={css(classes.tearsheet, stackLevelClassname)}
isOpen={isOpen}
variant={ModalVariant.large}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if you meant to include this? I'd remove it since it doesn't do anything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tearsheet - New component based on Modal

5 participants